import cgi
import os
import string
import random
import datetime, time
import math

from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.db import polymodel
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template
#from google.appengine.api import memcache

from google.appengine.api import users
from google.appengine.api import images
from google.appengine.api import mail

from django.utils import simplejson

################################################
# FUNCTIONS

def gen_code():
    chars = string.lowercase + string.digits
    chars = chars.replace('o','',1).replace('0','',1).replace('l','',1)
    code = ''.join([random.choice(chars) for _ in range(4)])
    query = Product.all()
    query.filter('code =', code)
    
    if query.get() is not None:
        return gen_code()
    else:
        return code

def next_page_url(url, page_requested):
    old_param = '='.join(['page', str(page_requested)])
    new_param = '='.join(['page', str(page_requested+1)])
    
    if url.find('page=') is not -1:
        url = url.replace(old_param, new_param)
    else:
        if url.find('?') is not -1:
            glue = '&'
        else:
            glue = '?'
        
        url = glue.join([url, new_param])
    
    return url

def prev_page_url(url, page_requested):
    old_param = '='.join(['page', str(page_requested)])
    new_param = '='.join(['page', str(page_requested-1)])
    
    if url.find('page=') is not -1:
        url = url.replace(old_param, new_param)
    else:
        if url.find('?') is not -1:
            glue = '&'
        else:
            glue = '?'
        
        url = glue.join([url, new_param])
    
    return url


################################################
# MODELS

class Algos(db.Model):
    algo = db.StringProperty()

class Brand(db.Model):
    name = db.StringProperty()
    logo_file_name = db.StringProperty()
    count = db.IntegerProperty(default=0)
    created = db.DateTimeProperty(auto_now_add=True)
    modified = db.DateTimeProperty(auto_now=True)
    
    @classmethod
    def get_active(self):
        return self.all().filter('count >', 0).fetch(1000)
        
    def countDown(self):
        if not self.count:
            self.count = 0
        
        self.count -= 1
        
        if self.count < 0:
            self.count = 0
        
        self.put()
        return self.count
        
    def countUp(self):
        self.count += 1
        self.put()
        return self.count

class Dealer(db.Model):
    name = db.StringProperty()

class Product(polymodel.PolyModel):
    '''Base class for all types of product'''
    
    name = db.StringProperty(required=True)
    quantity = db.FloatProperty()
    price = db.FloatProperty()
    code = db.StringProperty()
    dealer = db.ReferenceProperty(Dealer)
    brand = db.ReferenceProperty(Brand, default=None)
    pic = db.BlobProperty(default=None)
    created = db.DateTimeProperty(auto_now_add=True)

class TarimaComposition(db.Model):
    name = db.StringProperty()
    count = db.IntegerProperty(default=0)
    created = db.DateTimeProperty(auto_now_add=True)
    modified = db.DateTimeProperty(auto_now=True)
    
    @classmethod
    def is_more_than_one(self):
        return len(self.all().fetch(2)) > 1
        
    def countDown(self):
        if not self.count:
            self.count = 0
        
        self.count -= 1
        
        if self.count < 0:
            self.count = 0
        
        self.put()
        return self.count
        
    def countUp(self):
        self.count += 1
        self.put()
        return self.count

class Wood(db.Model):
    name = db.StringProperty()
    count = db.IntegerProperty(default=0)
    created = db.DateTimeProperty(auto_now_add=True)
    modified = db.DateTimeProperty(auto_now=True)
    
    @classmethod
    def get_active(self):
        return self.all().filter('count >', 0).fetch(1000)
        
    def countDown(self):
        if not self.count:
            self.count = 0
        
        self.count -= 1
        
        if self.count < 0:
            self.count = 0
        
        self.put()
        return self.count
        
    def countUp(self):
        self.count += 1
        self.put()
        return self.count

class Resistance(db.Model):
    name = db.StringProperty(default=None)

class Tarima(Product):
    composition = db.ReferenceProperty(TarimaComposition)
    wood = db.ReferenceProperty(Wood)
    resistance = db.ReferenceProperty(Resistance)
    
    @classmethod
    def get_top_price(self):
        prod = self.all().order('-price').get()
        if prod:
            return prod.price
        else:
            return 0
    
    @classmethod
    def get_min_price(self):
        prod = self.all().order('price').get()
        if prod:
            return prod.price
        else:
            return 0
    
    @classmethod
    def get_top_quantity(self):
        prod = self.all().order('-quantity').get()
        if prod:
            return prod.quantity
        else:
            return 0


################################################
# REQUEST HANDLERS

class InitModels(webapp.RequestHandler):
    '''
    Create one entity for every _empty_ model in the 
    datastore so it can be managed from the Datastore Viewer'''
    
    def get(self):
        if(self.request.get('pass') != 'cocacola'):
            self.response.out.write('<h1>Access denied</h1>')
        else:
            self.response.out.write('<h1>Access granted</h1>')
            self.response.out.write('I hereby begin to check every model one by one...<br />')
            
            if not TarimaComposition.all().get():
                item = TarimaComposition(name = 'laminada')
                item.put()
                self.response.out.write('TarimaComposition initialized<br />')
            
            if not Wood.all().get():
                woods = ['roble', 'haya', 'jatoba']
                for w in woods:
                    item = Wood(name = w)
                    item.put()
                self.response.out.write('Wood initialized<br />')
            
            if not Dealer.all().get():
                dealers = ['Todo tarimas']
                for d in dealers:
                    item = Dealer(name = d)
                    item.put()
                self.response.out.write('Dealer initialized<br />')
                
            if not Brand.all().get():
                brands = {'quick step': 'quickstep', 'pergo': 'pergo'}
                for n, l in brands.iteritems():
                    item = Brand(name=n, logo_file_name=l)
                    item.put()
                self.response.out.write('Brand initialized<br />')
                
            if not Resistance.all().get():
                resistances = ['RC4', 'RC5']
                for r in resistances:
                    item = Resistance(name = r)
                    item.put()
                self.response.out.write('Resistance initialized<br />')
                
        self.response.out.write('Finito<br />')


class FixStuffHandler(webapp.RequestHandler):
    '''Maintenance class. Just add anything and visit /adm/fix logged as admin.'''
    
    def get(self):
        pass

PAGESIZE = 10
class MainView(webapp.RequestHandler):
    '''This class takes care of the main listing view for admin and regular users'''
    
    def get(self, crap=None, dump=None):
        is_user_admin = users.is_current_user_admin()
        
        #####################################################################################
        # gather main template values 
        template_values = {
            'path': self.request.path,
            'qstr': self.request.query_string,
            'user': users.get_current_user(),
            'logout_url': users.create_logout_url('/'),
            'login_url': users.create_login_url(self.request.path),
            'is_user_admin': is_user_admin,
        }
        
        #####################################################################################
        # gather NEW PRODUCT FORM template values (only if current user is administrator)
        if is_user_admin:
            template_values.update({
                'entry_form_dealer_list': Dealer.all().order('name'),
                'entry_form_brand_list': Brand.all().order('name'),
                'entry_form_comp_list': TarimaComposition.all().order('name'),
                'entry_form_wood_list': Wood.all().order('name'),
                'entry_form_resistance_list': Resistance.all().order('name'),
                'entry_form_dealer_selected': self.request.get('entry_dealer'),
                'entry_form_brand_selected': self.request.get('entry_brand'),
            })
        
        #####################################################################################
        # FILTERS FORM 
        # This values are what's necessary to just render the form without any filtering.
        # Values related to users choice on filters are colected down below where, if filters
        # were selected, we build the query accordingly at the same time.
        
        # tarima compositions (show it only if is useful)
        template_values['show_compositions_filter'] = TarimaComposition.is_more_than_one()
        
        # max price filter
        min_price = Tarima.get_min_price()
        top_price = Tarima.get_top_price()
        step = top_price/5
        template_values['price_thresholds'] = []
        template_values['top_price'] = 0
        if step > 1:
            template_values['price_thresholds'] = range(math.ceil(min_price), float(top_price), step)
            template_values['top_price'] = top_price
        
        # BRAND filter 
        # unlike the entry form, for filters we need to show only those brands with prdos associated)
        template_values['filter_brand_list'] = Brand.get_active()
        
        # WOOD filter
        # unlike the entry form, for filters we need to show only those woods with prdos associated
        template_values['filter_wood_list'] = Wood.get_active()
        
        #####################################################################################
        # PRODUCT LIST
        # now we build query object and gather template values related to the product list which
        # could be... filtered, unfiltered, or just one prod (if code search was used)
        
        # is it a code search
        if self.request.get('code'):
            query = Product.all()
            query.filter('code =', self.request.get('code'))
            template_values['prods'] = query.fetch(1)
            template_values['searched_code'] = self.request.get('code')
        
        # then it is a product list
        else:
            # unfiltered query for tarimas
            query = Tarima.all()
            
            # now we add filter info, if any
            
            # by max price
            if self.request.get('filter-price-max'):
                query.filter('price <=', float(self.request.get('filter-price-max'))+1)
                template_values['filter_price_max'] = float(self.request.get('filter-price-max'))
                template_values['is_filter_active'] = True
            
            # by brand
            if self.request.get('filter-brand') and self.request.get('filter-brand') != 'all':
                if self.request.get('filter-brand') == 'None':
                    query.filter('brand =', None)
                    template_values['filter_brand_selected'] = "Sin Marca"
                else:
                    brand_key = self.request.get('filter-brand')
                    brand = db.get(brand_key)
                    query.filter('brand =', brand.key())
                    template_values['filter_brand_selected'] = brand.name
                
                template_values['is_filter_active'] = True
            
            # by wood
            if self.request.get('filter-wood') and self.request.get('filter-wood') != 'all':
                wood_key = self.request.get('filter-wood')
                wood = db.get(wood_key)
                query.filter('wood =', wood.key())
                template_values['filter_wood_selected'] = wood.name
                template_values['is_filter_active'] = True
            
            # by composition (flotante/maciza/laminada/todas)
            if self.request.get('filter-comp') and self.request.get('filter-comp') != 'all':
                comp_query = TarimaComposition.all()
                comp_key = comp_query.filter('name =', self.request.get('filter-comp')).get().key()
                query.filter('composition =', comp_key)
                template_values['filter_comp_selected'] = self.request.get('filter-comp')
                template_values['is_filter_active'] = True
            
            # order by creation time ONLY if no inequality filter has been applied
            if not self.request.get('filter-price-max'):
                query.order('price')
            
            # PAGINATE
            offset = 0
            page_requested = int(self.request.get('page', 1))
            next_page = 0
            prev_page = 0
            
            if page_requested < 1:
                page_requested = 1
                
            offset = PAGESIZE * (page_requested - 1)
            
            prods = query.fetch(PAGESIZE+1, offset)
            
            # if we actually got PAGESIZE+1 prods there needs to be a next_page
            # link and prods list is one element too many
            if len(prods) == PAGESIZE + 1:
                next_page = page_requested + 1
                prods = prods[:-1]
                template_values['next_page_href'] = next_page_url(self.request.url, page_requested)
                
            if page_requested >= 2:
                prev_page = page_requested - 1
                template_values['prev_page_href'] = prev_page_url(self.request.url, page_requested)
                
            template_values['prods'] = prods
            template_values['prev_page'] = prev_page
        
        path = os.path.join(os.path.dirname(__file__), 'templates/index.html')
        self.response.out.write(template.render(path, template_values))
        
    def post(self, crap=None):
        product = Tarima(name=self.request.get('name'), code=gen_code())
        
        if self.request.get('wood'):
            product.wood = db.get(self.request.get('wood')).key()
        
        if self.request.get('composition'):
            product.composition = db.get(self.request.get('composition')).key()
        
        if self.request.get('resistance'):
            product.resistance = db.get(self.request.get('resistance')).key()
        
        if self.request.get('quantity'):
            product.quantity = float(self.request.get('quantity').replace(',','.',1))
        
        if self.request.get('price'):
            product.price = float(self.request.get('price').replace(',','.'))
        
        if self.request.get('dealer'):
            product.dealer = db.get(self.request.get('dealer'))
        
        if self.request.get('brand'):
            product.brand = db.get(self.request.get('brand'))
        
        # product images
        if self.request.get('pic'):
            img_data = self.request.POST.get('pic').file.read()
            img = images.Image(img_data)
            img.resize(100, 1000)
            #percentage = (100.00/float(img.height)) * 75.00
            #crop_value = (percentage/100.00)
            #img.crop(0.0, 0.0, 1.0, float(crop_value))
            png_data = img.execute_transforms()
            product.pic = png_data
        
        product.put()
        
        # update related entities (this needs to be done after product.put)
        if product.brand:
            product.brand.countUp()
        
        if product.wood:
            product.wood.countUp()
            
        if product.composition:
            product.composition.countUp()
        
        self.redirect(self.request.path+'?entry_dealer='+self.request.get('dealer')+'&entry_brand='+self.request.get('brand'))

class PicView(webapp.RequestHandler):
    """Serve a product image from the datastore."""
    
    def get(self, prod_key):
        prod = db.get(prod_key)
        self.response.headers['Content-Type'] = 'image/png'
        self.response.out.write(prod.pic)

class DeleteView(webapp.RequestHandler):
    """
    Delete a product.
    TO DO: put everything into a transaction... this sucks.
    """
    
    def get(self, prod_key):
        product = db.get(prod_key)
        
        # update related entities
        product.brand.countDown()
        product.wood.countDown()
        product.composition.countDown()
        
        # now delete de product itself
        product.delete()
        
        self.redirect('/admin')

class ModifyView(webapp.RequestHandler):
    """Modify a product"""
    
    def get(self, prod_id):
        producto = Tarima.get_by_id(int(prod_id))
        
        # if no product found with that id, just end it
        if not producto:
            path = os.path.join(os.path.dirname(__file__), 'templates/modify.html')
            self.response.out.write(template.render(path, {'prod_id': prod_id}))
            return
        
        template_values = { 
            'product': producto
        }
        
        # gather values for MODIFY PRODUCT FORM
        template_values.update({
            'user': users.get_current_user(),
            'path': self.request.path,
            'is_user_admin': users.is_current_user_admin(),
            'entry_form_dealer_list': Dealer.all().order('name'),
            'entry_form_brand_list': Brand.all().order('name'),
            'entry_form_comp_list': TarimaComposition.all().order('name'),
            'entry_form_wood_list': Wood.all().order('name'),
            'entry_form_resistance_list': Resistance.all().order('name'),
        })
        
        path = os.path.join(os.path.dirname(__file__), 'templates/modify.html')
        self.response.out.write(template.render(path, template_values))
        
    def post(self, prod_id):
        product = Tarima.get_by_id(int(prod_id))
        
        # if product doesn't exist...
        if not product:
            self.response.out.write('too mal')
        
        # FIRST, UPDATE RELATED ENTITIES
        
        # BRAND
        # if product chenges to a new brand, we need to increase that brand count value by 1
        if self.request.get('brand') != '':
            newBrand = Brand.get(self.request.get('brand'))
            if newBrand:
                newBrand.countUp()
        
        # if product had a brand set, we need to decrease that brand count value by 1
        if product.brand:
            product.brand.countDown()
        
        # WOOD
        # if product chenges to a new wood, we need to increase that wood count value by 1
        if self.request.get('wood') != '':
            newWood = Wood.get(self.request.get('wood'))
            if newWood:
                newWood.countUp()
        
        # if product had a wood set, we need to decrease that wood count value by 1
        if product.wood:
            product.wood.countDown()
            
        # COMPOSITION
        # if product chenges to a new composition, we need to increase that composition count value by 1
        if self.request.get('composition') != '':
            newComp = TarimaComposition.get(self.request.get('composition'))
            if newComp:
                newComp.countUp()
        
        # if product had a composition set, we need to decrease that composition count value by 1
        if product.composition:
            product.composition.countDown()
        
        # SECOND, WE UPDATE PRODUCT PROPERTIES
        if self.request.get('name'):
            product.name = self.request.get('name')
            
        if self.request.get('wood'):
            product.wood = db.get(self.request.get('wood')).key()
        
        if self.request.get('composition'):
            product.composition = db.get(self.request.get('composition')).key()
        
        if self.request.get('resistance'):
            product.resistance = db.get(self.request.get('resistance')).key()
        
        if self.request.get('quantity'):
            product.quantity = float(self.request.get('quantity').replace(',','.',1))
        
        if self.request.get('price'):
            product.price = float(self.request.get('price').replace(',','.'))
        
        if not self.request.get('dealer'):
            product.dealer = None
        else:
            product.dealer = db.get(self.request.get('dealer'))
        
        if not self.request.get('brand'):
            product.brand = None
        else:
            product.brand = db.get(self.request.get('brand'))
        
        if self.request.get('code-generate-new'):
            product.code = gen_code()
            
        if self.request.get('pic-delete-flag') == '1':
            product.pic = None
        
        elif self.request.get('pic'):
            img_data = self.request.POST.get('pic').file.read()
            img = images.Image(img_data)
            img.resize(100, 1000)
            png_data = img.execute_transforms()
            product.pic = png_data
        
        # save the product
        product.put()
        
        # redirect back to product modify view self.redirect('/adm/modify/%s' % prod_id) NO, LO HE CAMBIADO A LA LISTA COMPLETA
        self.redirect('/admin')

class RPCHandler(webapp.RequestHandler):
    def post(self):
        args = simplejson.loads(self.request.body)
        
        vals = {}
        for a in args:
            vals[a['name']] = a['value']
        
        message = mail.EmailMessage()
        message.subject = "Outletdetarimas.es"
        message.sender = "Automatico <no-responder@outletdetarimas.es>"
        message.to = "presupuestos@pavimentosarquiservi.com"
        message.cc = "Formularios <mariajesus@outletdetarimas.es>"
        message.bcc = "Formularios <formularios.pavimentos@gmail.com>"
        message.reply_to = vals['email']
        message.body = template.render('templates/contacto_email_arqui.txt', vals)
        message.send()
                
        self.response.out.write("Gracias " + vals['nombre'] + ' ' + vals['email'])

class xRPCHandler(webapp.RequestHandler):
    '''Handler for all RPC requests and allows the RPCMethods to be called'''
    def __init__(self):
        webapp.RequestHandler.__init__(self)
        self.methods = RPCMethods()
        
    def post(self):
        args = simplejson.loads(self.request.body)
        func, args = args[0], args[1:]
        
        # restrict access to internal methods
        if func[0] == '_':
            self.error(403) # access denied
            return
            
        func = getattr(self.methods, func, None)
        if not func:
            self.error(404) # file not found
            return
            
        result = func(*args)
        self.response.out.write(simplejson.dumps(result))


class RPCMethods:
    """ Defines the methods that can be RPCed.
    NOTE: Do not allow remote callers access to private/protected "_*" methods.
    """
    
    def Add(self, *args):
        # The JSON encoding may have encoded integers as strings.
        # Be sure to convert args to any mandatory type(s).
        ints = [int(arg) for arg in args]
        return sum(ints)

class asdf(webapp.RequestHandler):
    def post(self):
        # Send email with request to Arquiservi
        '''message = mail.EmailMessage()
        message.subject = "Pedido de Informacion en Outletdetarimas.es"
        message.sender = "Automatico <no-responder@outletdetarimas.es>"
        message.to = "<ad.arquiservi@gmail.com>"
        #message.bcc = "Formularios <formularios.pavimentos@gmail.com>"
        vals = {}
        for n in self.request.arguments():
            vals[n] = self.request.get(n)
        #message.body = template.render('templates/contacto_email_arqui.txt', vals)
        #message.send()
        
        #path = os.path.join(os.path.dirname(__file__), 'templates/', 'contacto_sent.html')
        #self.response.out.write(template.render(path, vals))'''



class HelpView(webapp.RequestHandler):
    def get(self):
        template_values = {}
        
        path = os.path.join(os.path.dirname(__file__), 'templates/ayuda.html')
        self.response.out.write(template.render(path, template_values))

def main():
    application = webapp.WSGIApplication([
                                        ('/ayuda', HelpView),
                                        ('/rpc', RPCHandler),
                                        ('/', MainView),
                                        ('/img/([-\w]+)', PicView),
                                        ('/adm/delete/([-\w]+)', DeleteView),
                                        ('/adm/initmodels', InitModels),
                                        ('/adm/fix', FixStuffHandler),
                                        ('/adm/modify/(.*)', ModifyView),
                                        ('/([-\w]+)', MainView),
                                        ('/([-\w]+)/([-\w]+)', MainView),
                                        ],
                                       debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()
